/**
 * 
 */
package com.devmindset.legoframework.client.screen;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.allen_sauer.gwt.log.client.Log;
import com.devmindset.legoframework.client.mvc.LanguageService;
import com.devmindset.legoframework.client.mvc.LegoComponent;
import com.devmindset.legoframework.client.mvc.metadata.PlaceholderMapping;
import com.devmindset.legoframework.client.mvc.metadata.ScreenStory;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.ui.RootPanel;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.layout.Layout;

/**
 * Governs and manages the {@link ApplicationScreen} screens used throughout the application. 
 * <br/><br/>
 * 
 * After a {@link ApplicationScreen} has been added to the application, it needs to place itself on
 * the registeredScreens list.
 * <br/><br/>
 *  
 * @author <a href="mailto:ice.penov@devmindset.com">Ice Penov</a>
 */
public final class ApplicationScreenManager {
  
  private static List<ApplicationScreen> registeredScreens = new ArrayList<ApplicationScreen>();
  private static ApplicationScreen activeScreen;    
  private static String activeScreenLoadingMessage;
  private static Layout activeLayout;
  private static List<Canvas> activeModals = new ArrayList<Canvas>();
  
  public static String REMEMBER_ME_SCREEN = "remember_me_screen";
  
  /**
   * No public instantiation; static usage only.
   */
  private ApplicationScreenManager() {
    ;
  }
  
  public static void activateHomeScreen() {
  	activateHomeScreen(new HashMap<String, Object>());
  }
  
  /**
   * Finds the appropriate (last active or the default screen) to
   * activate, usually in the beginning of the application.
   * <br/><br/>
   * 
   * Uses a cookie to trace the clients state in the application,
   * and in case of browser refresh will reactivate the last screen
   * or will default to the first registered screen, if cookie not found.
   */
  public static void activateHomeScreen(Map<String, Object> initMap) {
  	setMarginToBody();
    activate(findLastActiveScreenForUser(), initMap);
  }
  
  private static native void setMarginToBody() /*-{
    $doc.body.style.margin = 0;
  }-*/;
  
  private static Class<? extends ApplicationScreen> findLastActiveScreenForUser() {
    Class<? extends ApplicationScreen> result = null;
    String cookieValue = Cookies.getCookie("current_screen");
    if (cookieValue != null) {
      for (ApplicationScreen screen : registeredScreens) {
        if (cookieValue.equals(screen.getClass().toString())) {
          result = screen.getClass();
          break;
        }
      }
    } else {
    	//try remember me.
    	cookieValue = Cookies.getCookie(REMEMBER_ME_SCREEN);
    	if (cookieValue != null) {
    		cookieValue = cookieValue.replace("\"", "");
    		for (ApplicationScreen screen : registeredScreens) {
    			if (cookieValue.equals(screen.getClass().toString())) {    				
    				result = screen.getClass();
    				break;
    			}
    		}
    	}
    }
    if (result == null) {
      //add support for default home screen.
      if (registeredScreens.size() > 0) {
        result = registeredScreens.get(0).getClass();
      } else {
      	String msg = "Cannot find at least one registered screen. Register at least one using ApplicationScreenManager.register()";
      	Log.error(msg);
      	SC.say(msg);
      }
    }
    return result;
  }
  
  /**
   * Registers a {@link ApplicationScreen}. 
   * <br/><br/>
   * The first one register is the 'home' or 'default' screen.
   * 
   * @param screen the {@link ApplicationScreen} to be registered
   */
  public static synchronized void register(ApplicationScreen screen) {
    if (registeredScreens != null) {
      if (registeredScreens.contains(screen)) {
        Log.warn("Screen already registered. Skipping registration: [ " + screen + " ]");        
      } else {
        registeredScreens.add(screen);
        Log.debug("Screen registered: [ " + screen + " ]");
      }
    } else {
    	String msg = "Passed screen for registration was null [ " + screen + " ]";
      Log.error(msg);
      SC.warn(msg);
    }    
  }
  
  public static void activate(Class<? extends ApplicationScreen> aClass) {
  	activate(aClass, new HashMap<String, Object>());
  }
  /**
   * Activates the given {@link ApplicationScreen}.
   * 
   * @param aClass the class representing the {@link ApplicationScreen} to be activated
   * @param initParams initialization parameters for the screen to be activated
   */
  public static void activate(Class<? extends ApplicationScreen> aClass, Map<String, Object> initParams) {
  	if (activeScreen == null) {
  		LanguageService.removeLanguageCookie();
  	} else {
    	LanguageService.appendLocale();    		
  	}
  	
    if (activeScreen != null) {
    	for (LegoComponent smartComponent : activeScreen.getComponents()) {
    		smartComponent.clearInitializedState();
    		smartComponent.destroy();
    	}
    }
    
    ApplicationScreen screenToBeActivated = findRegisteredScreen(aClass);
    if (screenToBeActivated != null) {
      Cookies.setCookie("current_screen", screenToBeActivated.getClass().toString());
      screenToBeActivated.initialize(initParams);
      setActiveScreenLoadingMessage(screenToBeActivated.loadingMessage());
      updateLoadingMessage();
      RootPanel.get("loadingWrapper").setVisible(true);
      Log.debug("Activating Screen: " + screenToBeActivated.myStory());
      RootPanel.get().clear();
      if (activeModals.size() > 0) {
        for (Canvas modal : activeModals) {
          modal.destroy();
        }
        activeModals.clear();
      }
      Log.debug("Loading message placed. Firing construction events.");
      activeScreen = screenToBeActivated;
      activeLayout = screenToBeActivated.drawLayout();
      Log.debug("Layout prepared for screen: [ " + screenToBeActivated.myStory() + " ]");
      screenToBeActivated.fireConstructionEvents();
    } else {
    	String msg = "Error while activating screen - Unable to find registered screen [ " + aClass + " ]. Have you registered it?";
      Log.error(msg);
      SC.warn(msg);
    }
  }
  
  public static native void updateLoadingMessage() /*-{
    var msg = @com.devmindset.legoframework.client.screen.ApplicationScreenManager::getActiveScreenLoadingMessage()();
    var element = $doc.getElementById('loadingMsg');
    if (element) {
      element.innerHTML = msg;
    }
  }-*/;  
  
  protected static void placePreparedLayout() {
	  RootPanel.get().clear();
	  RootPanel.get().add(activeLayout);
	  Log.debug("Prepared layout activated.");
  }

  /**
   * Finds the registered screen by the given class.
   *  
   * @param aClass the class of the {@link ApplicationScreen} 
   * @return the registered screen if present, null otherwise
   */
  private static ApplicationScreen findRegisteredScreen(Class<? extends ApplicationScreen> aClass) {
    ApplicationScreen result = null;
    for (ApplicationScreen registeredScreen : registeredScreens) {
      if (aClass.equals(registeredScreen.getClass())) {
        result = registeredScreen;
        break;
      }
    }
    return result;
  }
  
  public static void signalCompletionToActiveScreen(LegoComponent component) {
	  if (canScreenhandleIt(activeScreen, component.getClass())) {
		  activeScreen.signalCompletion(component);		  
	  }
  }
  
  /**
   * Places the prepared component on the appropriate position
   * on the active screen.
   * 
   * @param component the {@link LegoComponent} to be placed
   * @param canvas the rendered {@link Canvas} to be placed on the active {@link ApplicationScreen}
   */
  public static void placePreparedComponent(LegoComponent component, Canvas canvas) {
    if (canScreenhandleIt(activeScreen, component.getClass())) {
      Log.debug("Placing component [ " + component.myStory() 
          + " ] to screen [ " + activeScreen.myStory() + " ]");
      placeComponentUsingMappings(component, canvas);
      activeScreen.addComponent(component);
      if (canvas instanceof Window) {
      	canvas.show();
      	activeModals.add(canvas);
      }
    } else if (!(canvas instanceof Window)){
    	String msg = "Unable to place component [ " + component.myStory() 
      	+ " ] to currently active screen [ " + activeScreen.myStory() + " ]. " +
      			" Passed component was not found in consistsOf clause of the screen [ " + activeScreen.myStory() + " ]"; 
      Log.error(msg);
      SC.warn(msg);
    }
  }
  
  public static void placeComponentUsingMappings(LegoComponent component, Canvas canvas) {
  	PlaceholderMapping[] mappings = activeScreen.myStory().getPlaceholderMappings();
  	List<PlaceholderMapping> list = Arrays.asList(mappings);
  	if (mappings == null) {
  		String msg = "Unable to find placeholder mappings for screen [ " + activeScreen.myStory() + " ]";
  		Log.error(msg);
  		SC.warn(msg);
  	} else {
  		if (hasDefinedPlaceholderMapping(list, component, canvas)) {
  			;
  		} else if (!(canvas instanceof Window)) {
  			String msg = "Unable to find placeholder mapping for component [ " + component.myStory() + " ] for screen [" + activeScreen.myStory() + " ]";
  			Log.error(msg);
  			SC.warn(msg);
  		}
  	}
  }
  
	private static boolean hasDefinedPlaceholderMapping(List<PlaceholderMapping> list, LegoComponent component, Canvas canvas) {
  	boolean flag = false;
  	for (PlaceholderMapping mapping : list) {
  		Class<? extends LegoComponent>[] components = mapping.getComponents();
  		for (Class<? extends LegoComponent> c : components) {
  			if (c.equals(component.getClass())) {
  				flag = true;
  				if (mapping.getPlaceholder().isAnimated()) {
  					mapping.getPlaceholder().placeAnimated(canvas);
  				} else {
  					mapping.getPlaceholder().place(canvas);
  				}
  				activeScreen.getPlaceholderMap().put(mapping.getPlaceholder(), c);
  				break;
  			}
  		}
  	}
  	return flag;
  }

  /**
   * Checks whether the given {@link ApplicationScreen} can contain and place the
   * given {@link LegoComponent} class.
   * 
   * @param screen the {@link ApplicationScreen} to be checked
   * @param class1 the Class of the {@link LegoComponent} subject to the check
   *  
   * @return true if can handle it, false otherwise 
   */
  private static boolean canScreenhandleIt(ApplicationScreen screen,
      Class<? extends LegoComponent> class1) {
    
    boolean result = false;    
    ScreenStory story = screen.myStory();
    LegoComponent[] declared = story.getConsistsOf();
    for (int i = 0; i < declared.length; i++) {
      if (declared[i].getClass().equals(class1)) {
        result = true;
        break;
      }
    }
    return result;
  }

  public static String getActiveScreenLoadingMessage() {
    return activeScreenLoadingMessage;
  }

  public static void setActiveScreenLoadingMessage(String activeScreenLoadingMessage) {
    ApplicationScreenManager.activeScreenLoadingMessage = activeScreenLoadingMessage;
  }

	public static boolean alreadyOnPlaceholder(LegoComponent smartComponent) {
		return activeScreen.getPlaceholderMap().containsValue(smartComponent.getClass());
	}  
  
}